package com.panopset.swing;

import static com.panopset.Util.log;

import java.awt.Font;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import javax.swing.JCheckBoxMenuItem;
import javax.swing.JComponent;
import javax.swing.JMenuItem;
import javax.swing.border.TitledBorder;

/**
 * Provide set of small, medium, and large fonts.
 *
 * @author Karl Dinwiddie
 */
public final class FontManager {

    public static String LUCIDIA = "Lucida Sans Regular";
    public static String LUCIDIA_BOLD = "Lucida Sans Bold";
    public static String LUCIDIA_OBLIQUE = "Lucida Sans Oblique";
    public static String LUCIDIA_BOLD_OB = "Lucida Sans Bold Oblique";

    public static String L_BRIGHT = "Lucida Bright Regular";
    public static String L_BRIGHT_BOLD = "Lucida Bright Bold";
    public static String L_BRIGHT_OBLIQUE = "Lucida Bright Italic";
    public static String L_BRIGHT_BOLD_OB = "Lucida Bright Bold Italic";

    public static String MONOSPACE = "Lucida Sans Typewriter Regular";
    public static String MONOSPACE_BOLD = "Lucida Sans Typewriter Bold";
    public static String MONOSPACE_OBLIQUE = "Lucida Sans Typewriter Oblique";
    public static String MONOSPACE_BOLD_OB = "Lucida Sans Typewriter Bold Oblique";

    /**
     * Arial.
     */
    public static final String ARIAL = "Arial";
    /**
     * Dialog.
     */
    public static final String DIALOG = "Dialog";
    /**
     * DocumentManager key.
     */
    public static final String KEY = "com.panopset.fontsize";

    private Font monospace;

    private Font boldArial;
    private Font plainArial;
    private Font borderTitle;

    /**
     * @param size
     *            Size.
     */
    public void setSize(final Size size) {
        if (size == null) {
            return;
        }
        monospace = new Font(MONOSPACE, Font.PLAIN, size.getValue());
        boldArial = new Font(ARIAL, Font.BOLD, size.getValue());
        plainArial = new Font(ARIAL, Font.PLAIN, size.getValue());
        int dip = 2;
        if (FontManager.Size.SMALL.val == size.val) {
            dip = 1;
        }
        borderTitle = new Font(ARIAL, Font.ITALIC, size.getValue() - dip);
        updateAll();
        for (final FontManager.Size s : FontManager.Size.values()) {
            JMenuItem mi = s.getMenuItem();
            if (s == size) {
                mi.setSelected(true);
            } else {
                mi.setSelected(false);
            }
        }
    }

    /**
     * @param sizeString
     *            New size.
     */
    public void setSize(final String sizeString) {
        setSize(Size.find(com.panopset.Util.parseInt(sizeString)));
    }

    /**
     * @param size
     *            Size.
     */
    public static void setDefaultSize(final Size size) {
        defaultSize = size;
    }

    /**
     * Default size.
     */
    private static Size defaultSize = Size.SMALL;

    /**
     * @return Monospace font.
     */
    public static Font getMonospace() {
        return getInstance().monospace;
    }

    /**
     * @return Bold Arial font.
     */
    public static Font getBoldArial() {
        return getInstance().boldArial;
    }

    /**
     * @return Plain Arial font.
     */
    public static Font getPlainArial() {
        return getInstance().plainArial;
    }

    /**
     * @return Plain Arial font.
     */
    public static Font getBorderTitle() {
        return getInstance().borderTitle;
    }

    /**
     * Font classifications.
     */
    public enum FontType {

        /**
         * Plain Arial.
         */
        plainArial,
        /**
         * Bold Arial.
         */
        boldArial,
        /**
         * Monospace.
         */
        monospace,
        /**
         * Border title.
         */
        borderTitle
    }

    /**
     * SMALL, MEDIUM, LARGE.
     */
    public static enum Size {

        /**
         * Small size is 9.
         */
        SMALL(9),
        /**
         * Medium size is 12.
         */
        MEDIUM(12),
        /**
         * Large size is 16.
         */
        LARGE(16),
        /**
         * Extra large size is 24.
         */
        SUPER(24),
        /**
         * Cinema size is 48.
         */
        CINEMA(48);
        /**
         * Value.
         */
        private final int val;

        /**
         * @param fontSize
         *            Font size.
         */
        Size(final int fontSize) {
            val = fontSize;
        }

        /**
         * Find size.
         *
         * @param fontSize
         *            Required font size.
         * @return Next smallest size if no match found.
         */
        public static Size find(final int fontSize) {
            for (Size size : Size.values()) {
                if (size.getValue() == fontSize) {
                    return size;
                }
            }
            for (Size size : Size.values()) {
                if (size.getValue() > fontSize) {
                    return size;
                }
            }
            return defaultSize;
        }

        /**
         * @return Font size.
         */
        public int getValue() {
            return val;
        }

        private JMenuItem mi;

        public JMenuItem getMenuItem() {
            if (mi == null) {
                mi = new JCheckBoxMenuItem(name());
            }
            return mi;
        }
    }

    /**
     * @param component
     *            to register.
     * @param fontType
     *            Font type.
     */
    private void registerComponent(final JComponent component,
            final FontType fontType) {
        if (component == null || fontType == null) {
            throw new RuntimeException();
        }
        getCmps().put(component, fontType);
        component.setFont(getFontForType(fontType));
    }

    /**
     * @param component
     *            Component.
     * @param fontType
     *            Font type.
     */
    public static void register(final JComponent component,
            final FontType fontType) {
        getInstance().registerComponent(component, fontType);
    }

    /**
     * @param border
     *            Titled border.
     * @param fontType
     *            Font type.
     */
    public static void register(final TitledBorder border,
            final FontType fontType) {
        getInstance().registerTitleBorder(border, fontType);
    }

    /**
     * @param border
     *            to register.
     * @param fontType
     *            Font type.
     */
    private void registerTitleBorder(final TitledBorder border,
            final FontType fontType) {
        if (border == null || fontType == null) {
            throw new RuntimeException();
        }
        getBrdrs().put(border, fontType);
        border.setTitleFont(getFontForType(fontType));
    }

    /**
     * @param fontType
     *            Font type.
     * @return Font.
     */
    public static Font getFont(final FontType fontType) {
        return getInstance().getFontForType(fontType);
    }

    /**
     * @param fontType
     *            Font type.
     * @return Font.
     */
    private Font getFontForType(final FontType fontType) {
        try {
            Object rtn = FontManager.class.getMethod(
                    "get"
                            + com.panopset.Strings
                                    .upperCaseFirstLetter(fontType.name()),
                    new Class<?>[0]).invoke(this, new Object[0]);
            if (rtn instanceof Font) {
                Font f = (Font) rtn;
                return f;
            }
            throw new NullPointerException();
        } catch (Exception ex) {
            log(ex);
            throw new RuntimeException(ex);
        }
    }

    /**
     * Update all registered fonts.
     */
    public synchronized void updateAll() {
        for (Entry<JComponent, FontType> e : getCmps().entrySet()) {
            e.getKey().setFont(getFontForType(e.getValue()));
            e.getKey().invalidate();
        }
        for (Entry<TitledBorder, FontType> e : getBrdrs().entrySet()) {
            e.getKey().setTitleFont(getFontForType(e.getValue()));
        }
    }

    /**
     * @return Registered components.
     */
    private Map<JComponent, FontType> getCmps() {
        if (cmps == null) {
            cmps = new HashMap<JComponent, FontType>();
        }
        return cmps;
    }

    /**
     * @return Registered borders.
     */
    private Map<TitledBorder, FontType> getBrdrs() {
        if (brdrs == null) {
            brdrs = new HashMap<TitledBorder, FontType>();
        }
        return brdrs;
    }

    /**
     * Components.
     */
    private static Map<JComponent, FontType> cmps;
    /**
     * Borders.
     */
    private static Map<TitledBorder, FontType> brdrs;

    /**
     * Start with default size.
     */
    private FontManager() {
        setSize(defaultSize);
    }

    /**
     * Based on SingletonHolder inner class by Bill Pugh.
     *
     *
     * <h5>References</h5>
     * <ul>
     * <li>
     * <a href="http://en.wikipedia.org/wiki/Singleton_pattern">
     * http://en.wikipedia.org/wiki/Singleton_pattern </a></li>
     * </ul>
     *
     */
    private static final class Singleton {

        /**
         * Instance variable.
         */
        private static final FontManager INSTANCE = new FontManager();

        /**
         * Private constructor.
         */
        private Singleton() {
            // Prevent instantiation.
        }
    }

    /**
     * @return static Random instance.
     */
    public static FontManager getInstance() {
        return Singleton.INSTANCE;
    }
}
